package br.unifor.cmi.controller.server.impl.jgcs;


//Classes a mudar de pacote ***///

import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;

import javax.naming.Context;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import net.sf.jgcs.jmx.JmxConfigurator;
import net.sf.jgcs.utils.FactoryUtil;

import org.ow2.cmi.admin.MBeanUtils;
import org.ow2.cmi.controller.common.CMIThreadFactory;
import org.ow2.cmi.controller.server.AbsServerClusterViewManager;
import org.ow2.cmi.controller.server.DistributedObjectInfo;
import org.ow2.cmi.controller.server.IServerConfig;
import org.ow2.cmi.controller.server.ServerClusterViewManagerException;
import org.ow2.cmi.controller.server.filter.IFilter;
import org.ow2.cmi.controller.server.filter.IdentityFilter;
import org.ow2.cmi.controller.server.impl.jgroups.IIntercepted;
import org.ow2.cmi.controller.server.impl.jgroups.ResumableRunnable;
import org.ow2.cmi.lb.data.PolicyData;
import org.ow2.cmi.lb.loader.ArchiveWrapper;
import org.ow2.cmi.reference.CMIReference;
import org.ow2.cmi.reference.ObjectNotFoundException;
import org.ow2.cmi.reference.ServerId;
import org.ow2.cmi.reference.ServerNotFoundException;
import org.ow2.cmi.reference.ServerRef;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import br.unifor.util.cluster.jgcs.ConnectionManager;
import br.unifor.util.cluster.jgcs.IDistributedTree;
import br.unifor.util.cluster.jgcs.IDistributedTree.DistributedTreeListener;
import br.unifor.util.cluster.jgcs.IDistributedTree.ViewListener;

/**
 * Manager implemented with JGroups that contains CMI logic for the server-side.
 *
 * @author The new CMI team
 * @see DistributedTree
 */
@ThreadSafe
public final class JGCSClusterViewManager extends
        AbsServerClusterViewManager implements DistributedTreeListener,
        ViewListener {

    /**
     * Logger.
     */
    private static final Log LOGGER = LogFactory
            .getLog(JGCSClusterViewManager.class);
    
    private static final String JGCS_PROPERTIES = "cmi.server.impl.jgcs.properties";

    /**
     * Distributed tree from JGCS.
     */
    private IDistributedTree distributedTree;

    /**
     * Local address
     */
    private SocketAddress localAddress;

    /**
     * A set of paths to delete by the dedicated thread.
     */
    @GuardedBy("pathsToDelete")
    private final Set<String> pathsToDelete = Collections
            .synchronizedSet(new HashSet<String>());

    /**
     * A daemon to clean the distributedTree.
     */
    private Thread garbageThread;

    /**
     * The handle to resume the execution of the garbage thread.
     */
    private ResumableRunnable resumableRunnable;

    /**
     * A mutex.
     */
    private final Object lock = new Object();

    /**
     * A connection manager to handle accesses to distributed tree.
     */
    private ConnectionManager connectionManager;
    
    /**
     * JGCS JMX Configurator
     */
    private JmxConfigurator jmxConfig = null;

    /**
     * Construct a JGroupsClusterViewManager.
     */
    public JGCSClusterViewManager() {
    	
    }

    /**
     * Start the server-side manager.
     */
    @Override
    public void doStart() throws ServerClusterViewManagerException {

        String jgcsProperties = System.getProperty(JGCS_PROPERTIES);
        if(jgcsProperties == null) {
        	jgcsProperties = ((Config) getConfig()).getJgcsProperties();
        }

        long reconTimeout = ((Config) getConfig()).getRecoTimeout();
        LOGGER.debug(
                "Reconnection timeout for JGCS DistributedTree is :{0} ms",
                reconTimeout);
        // Create a new DistributedTree
        IDistributedTree distributedTreeCopy = new SynchronizedDistributedTree(jgcsProperties);
        // Add this object as a DistributedTreeListener
        distributedTreeCopy.addDistributedTreeListener(this);
        // Add this object as a ViewListener
        distributedTreeCopy.addViewListener(this);
        connectionManager = new ConnectionManager(reconTimeout,
                distributedTreeCopy, IIntercepted.class);
        distributedTree = (IDistributedTree) Proxy.newProxyInstance(
                IDistributedTree.class.getClassLoader(),
                new Class[] {IDistributedTree.class}, connectionManager);
        // Creates the thread that will clean the distributed tree
        CMIThreadFactory cmiThreadFactory = getCmiThreadFactory();
        resumableRunnable = new TreeCleaner();
        garbageThread = cmiThreadFactory.newThread(resumableRunnable, "Distributed Tree Cleaner");

        // Start the DistributedTree
        try {
            distributedTreeCopy.start();
        } catch (Exception e) {
            LOGGER.error("Cannot start the distributed tree", e);
            throw new ServerClusterViewManagerException("Cannot start the distributed tree", e);
        }

        distributedTreeCopy.setMembershipListener(connectionManager);
        connectionManager.setControlSession(distributedTreeCopy.getControlSession());
        
        if(getConfig().isAdminEnabled()) {
        	LOGGER.info("Starting JMX..");
        	FactoryUtil jgcsConf;
			try {
				jgcsConf = new FactoryUtil(jgcsProperties);
	        	// Register the JMX MBeans for plugged-in TOOLKIT
	        	jmxConfig = (JmxConfigurator) jgcsConf.getInstance("jgcsJmx");
	        	if(jmxConfig != null){
	            	jmxConfig.register(	distributedTreeCopy.getDataSession(), 
			    						distributedTreeCopy.getControlSession(),
			    						distributedTreeCopy.getService(),
			    						distributedTreeCopy.getProtocol(),
			    						MBeanUtils.getMBeanServer(),
			    						MBeanUtils.getMBeanDomainName());	        		
	        	}
            
            } catch (Exception e) {
                LOGGER.warn("Unable to start jgcs JMX",  e);
            }
        }

        
        // Get local address
        SocketAddress obj = distributedTreeCopy.getLocalAddress();
        localAddress = obj;
        if (obj == null) {
            LOGGER.error("Cannot get the local address");
            throw new ServerClusterViewManagerException(
                    "Cannot get the local address");
        } else{
            LOGGER.debug("Address: {0}", localAddress);
        } 
        setImplementationAvailable(true);
        garbageThread.start();
    }

    /**
     * Stop the server-side manager.
     */
    @Override
    public void doStop() {
        setImplementationAvailable(false);
        resumableRunnable.stopExecution();
    	if(jmxConfig != null){
    		LOGGER.info("Stoping JMX..");
        	try {
				jmxConfig.unregister(	distributedTree.getDataSession(), 
										distributedTree.getControlSession(),
										distributedTree.getService(),
										distributedTree.getProtocol(),
										MBeanUtils.getMBeanServer(),
										MBeanUtils.getMBeanDomainName()	);
			} catch (Exception e) {
				LOGGER.error("ERROR WHILE UNREGISTERING JMX...", e);
			}	        		
    	}
        connectionManager.setTimeout(0);
        distributedTree.stop();
    }


    public boolean isRegistrationOpened() {
        return isImplementationAvailable();
    }

    @Override
    protected void initServerConfig(final String initialContextFactoryName, final ServerId serverId, final Context context)
    throws ServerClusterViewManagerException {
        registerPath(serverId.getProtocol(), getServerConfigPath(serverId));
        super.initServerConfig(initialContextFactoryName, serverId, context);
    }

    /**
     * Removes the given path from the distributedTree.
     *
     * @param pathToDelete
     *            a path in the distributedTree
     */
    private void removePath(final String pathToDelete) {
        distributedTree.remove(pathToDelete);
    }

    /**
     * Returns true if the given object is already replicated.
     *
     * @param objectName
     *            a name of object
     * @return true if the given object is already replicated
     */
    @Override
    protected boolean containObject(final String objectName) {
        try {
            return distributedTree.exists(getObjectPath(objectName));
        } catch (Exception e) {
            LOGGER
                    .debug(
                            "The distributed tree is not available. The object {0} is considered as missing",
                            objectName, e);
            return false;
        }
    }

    /**
     * Returns informations on this object.
     *
     * @param objectName
     *            a name of object
     * @return informations on this object
     * @throws ObjectNotFoundException
     *             if the given object is not found
     */
    @Override
    protected DistributedObjectInfo getDistributedObjectInfo(
            final String objectName) throws ObjectNotFoundException {
        String objectPath = getObjectPath(objectName);
        Object obj = distributedTree.get(objectPath);
        if (obj == null) {
            LOGGER.error("Unknown object with name {0}", objectName);
            throw new ObjectNotFoundException("Unknown object with name "
                    + objectName);
        }
        try {
            return ((DistributedObjectInfo) obj).clone();
        } catch (CloneNotSupportedException e) {
            throw new Error("Amazing bug Garotinho... I don't believe it !", e);
        }
    }

    /**
     * Add informations on the clustered object with the given name.
     *
     * @param objectName
     *            a name of object
     * @param distributedObjectInfo
     *            informations on the clustered object
     */
    @Override
    protected void addDistributedObjectInfo(final String objectName,
            final DistributedObjectInfo distributedObjectInfo) {
        String objectPath = getObjectPath(objectName);
        LOGGER.debug("Adding {0} at the path {1}", distributedObjectInfo,
                objectPath);
        distributedTree.add(objectPath, distributedObjectInfo);
    }

    /**
     * Sets informations on the clustered object with the given name.
     *
     * @param objectName
     *            a name of object
     * @param distributedObjectInfo
     *            informations on the clustered object
     */
    @Override
    protected void setDistributedObjectInfo(final String objectName,
            final DistributedObjectInfo distributedObjectInfo) {
        String objectPath = getObjectPath(objectName);
        LOGGER.debug("Adding {0} at the path {1}", distributedObjectInfo,
                objectPath);
        LOGGER.debug("Old infos: {0}", distributedTree.get(objectPath));
        distributedTree.set(objectPath, distributedObjectInfo);
    }

    /**
     * Registers an association between a key and a path.
     * @param key a key
     * @param path a path
     */
    private void registerPath(final String key, final String path) {
        String hostname = localAddress.toString();
        String address = getAddressPath(hostname);
        String instAddr = getInstAddrPath(address, key);
        distributedTree.add(instAddr, path);
    }

    /**
     * Unregisters an association between a key and a path.
     * @param key a key
     */
    private void unRegisterPath(final String key) {
        String hostname = localAddress.toString();
        String address = getAddressPath(hostname);
        String instAddr = getInstAddrPath(address, key);
        distributedTree.remove(instAddr);
    }

    /**
     * Returns a list of CMIReference for an object with the given name and protocol.
     * The objects deployed on a blacklisted server are discarded.
     * @param objectName a name of object
     * @param protocolName a name of protocol
     * @return a list of CMIReference for an object with the given name and protocol
     * @throws ObjectNotFoundException if none object has the given name for the given protocol
     */
    public final List<CMIReference> getCMIReferences(final String objectName, final String protocolName)
    throws ObjectNotFoundException {
        String objectPath = getObjectPath(objectName);
        String protoPath = getProtocolPath(objectPath, protocolName);
        List<CMIReference> cmiReferences = new ArrayList<CMIReference>();
        Vector<String> childrenNames = distributedTree
                .getChildrenNames(protoPath);
        if (childrenNames.isEmpty()) {
            LOGGER.error("Unknown object {0} or protocol {1}", objectName,
                    protocolName);
            throw new ObjectNotFoundException("Unknown object " + objectName
                    + " or protocol " + protocolName);
        }
        for (String childrenName : childrenNames) {
            CMIReference cmiReference = getCMIReference(protoPath, childrenName);
            cmiReferences.add(cmiReference);
        }
        return cmiReferences;
    }

    /**
     * Returns a list of CMIReference for an object with the given name.
     *
     * @param objectName
     *            a name of object
     * @return a list of CMIReference for an object with the given name
     * @throws ObjectNotFoundException
     *             if no object is bound with the given name
     */
    public List<CMIReference> getCMIReferences(final String objectName)
            throws ObjectNotFoundException {
        String objectPath = getObjectPath(objectName);
        ArrayList<CMIReference> cmiReferences = new ArrayList<CMIReference>();
        Vector<String> protocols = distributedTree.getChildrenNames(objectPath);
        if (protocols.isEmpty()) {
            LOGGER.error("Unknown object {0}", objectName);
            throw new ObjectNotFoundException("Unknown object " + objectName);
        }
        for (String p : protocols) {
            String protoPath = getProtocolPath(objectPath, p);
            Vector<String> instances = distributedTree
                    .getChildrenNames(protoPath);
            for (String nodeName : instances) {
                cmiReferences.add(getCMIReference(protoPath, nodeName));
            }
        }
        return cmiReferences;
    }

    /**
     * Return the reference for a given path for protocols and node name.
     *
     * @param protocolPath
     *            the path for protocols
     * @param nodeName
     *            a node name
     * @return the reference for a given protocol and node
     */
    private CMIReference getCMIReference(final String protocolPath,
            final String nodeName) {
        String cmiReferencePath = getCMIReferencePath(protocolPath, nodeName);
        return (CMIReference) distributedTree.get(cmiReferencePath);
    }

    /**
     * Adds a CMIReference to the cluster view.
     *
     * @param cmiReference
     *            a reference on an instance
     */
    public void addCMIReference(final CMIReference cmiReference) {
        ServerRef serverRef = cmiReference.getServerRef();
        if (!(serverRef instanceof ServerId)) {
            LOGGER.error("Not a valid CMIReference: {0} is not a server id.",
                    serverRef);
            throw new IllegalArgumentException("Not a valid CMIReference: "
                    + serverRef + " is not a server id.");
        }
        String path = getCMIReferencePath(cmiReference);
        distributedTree.add(path, cmiReference);
        registerPath(
                serverRef.getProtocol() + "_" + cmiReference.getObjectName(),
                path);
    }

    /**
     * Removes a CMIReference from the cluster view.
     *
     * @param cmiReference
     *            a reference on a clustered object
     * @return true if no more server have the object
     */
    @Override
    protected boolean doRemoveCMIReference(final CMIReference cmiReference) {
        LOGGER.debug("Removing {0}...", cmiReference);
        ServerRef serverRef = cmiReference.getServerRef();
        String objectName = cmiReference.getObjectName();
        String objectPath = getObjectPath(objectName);
        String refPath = getCMIReferencePath(cmiReference);
        String protocolPath = getProtocolPath(objectPath, serverRef.getProtocol());
        distributedTree.remove(refPath);
        unRegisterPath(serverRef.getProtocol() + "_" + objectName);

        // Other servers with the same protocol ?
        if (distributedTree.getChildrenNames(protocolPath).isEmpty()) {
            distributedTree.remove(protocolPath);
            // Other protocols for this object ?
            if(distributedTree.getChildrenNames(objectPath).isEmpty()) {
                distributedTree.remove(objectPath);
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the set of name of cluster.
     *
     * @return a set of name of cluster
     */
    public Set<String> getClusterNames() {
        HashSet<String> clusterNames = new HashSet<String>();
        Vector<String> objectNames = distributedTree
                .getChildrenNames(getObjectsPath());
        DistributedObjectInfo distributedObjectInfo;
        for (String objectName : objectNames) {
            try {
                distributedObjectInfo = getDistributedObjectInfo(objectName);
                clusterNames.add(distributedObjectInfo.getClusterName());
            } catch (ObjectNotFoundException e) {
                LOGGER.debug("Following Exception occurred: ", e);
            }
        }
        return clusterNames;
    }

    /**
     * Returns the set of name of object for a given name of cluster.
     *
     * @param clusterName
     *            a name of cluster
     * @return the set of name of object for a given name of cluster
     */
    public Set<String> getObjectNames(final String clusterName) {
        HashSet<String> objectNamesInCluster = new HashSet<String>();
        Vector<String> objectNames = distributedTree
                .getChildrenNames(getObjectsPath());
        DistributedObjectInfo distributedObjectInfo;
        for (String objectName : objectNames) {
            try {
                distributedObjectInfo = getDistributedObjectInfo(objectName);
                if (distributedObjectInfo.getClusterName().equals(clusterName)) {
                    objectNamesInCluster.add(objectName);
                }
            } catch (ObjectNotFoundException e) {
                LOGGER.debug("Following Exception occurred: ", e);
            }
        }
        return objectNamesInCluster;
    }

    /**
     * @return the set of name of object
     */
    public Set<String> getObjectNames() {
        Vector<String> objectNames = distributedTree
                .getChildrenNames(getObjectsPath());
        return new HashSet<String>(objectNames);
    }

    /**
     * Returns the time between each update of the cluster view by clients.
     *
     * @return the time between each update of the cluster view by clients
     */
    public int getDelayToRefresh() {
        return (Integer) distributedTree.get(getDelayPath());
    }

    /**
     * Sets the time between each update of the cluster view by clients.
     *
     * @param delay
     *            the time between each update of the cluster view by clients
     */
    public void setDelayToRefresh(final int delay) {
        distributedTree.reset(getDelayPath(), delay);
    }

    /* (non-Javadoc)
     * @see org.ow2.cmi.controller.server.AbsServerClusterViewManager#initDelayToRefresh(int)
     */
    @Override
    protected void initDelayToRefresh(final int delayToRefresh) {
        distributedTree.add(getDelayPath(), delayToRefresh);
    }

    /**
     * Returns true if the pool for object with the given name should be empty.
     *
     * @param objectName
     *            a name of object
     * @return true if the pool for object with the given name should be empty
     */
    public boolean isPoolToEmpty(final String objectName) {
        return distributedTree.exists(getPoolToEmptyPath(objectName));
    }

    /**
     * Adds the pool of the object with the given name of the list of pool that
     * should be empty.
     *
     * @param objectName
     *            a name of object
     */
    public void addPoolToEmpty(final String objectName) {
        distributedTree.add(getPoolToEmptyPath(objectName));
    }

    /**
     * Removes the pool of the object with the given name of the list of pool
     * that should be empty.
     *
     * @param objectName
     *            a name of object
     */
    public void removePoolToEmpty(final String objectName) {
        distributedTree.remove(getPoolToEmptyPath(objectName));
    }

    /**
     * Return true if the server with the given reference if blacklisted.
     *
     * @param serverRef
     *            a reference on a server
     * @return true the server with the given reference if blacklisted
     */
    public boolean isServerBlackListed(final ServerRef serverRef) {
        return distributedTree.exists(getServerBlackListedPath(serverRef));
    }

    /**
     * Add a server to the blacklist.
     *
     * @param serverRef
     *            a reference on a server
     */
    public void addServerToBlackList(final ServerRef serverRef) {
        distributedTree.add(getServerBlackListedPath(serverRef));
    }

    /**
     * Remove a server to the blacklist.
     *
     * @param serverRef
     *            a reference on a server
     */
    public void removeServerFromBlackList(final ServerRef serverRef) {
        distributedTree.remove(getServerBlackListedPath(serverRef));
    }

    /**
     * Returns the load-factor for the server with the given address.
     *
     * @param serverRef
     *            a reference on a server
     * @return the load-factor for the server with the given address
     * @throws ServerNotFoundException
     *             if none server has the given address
     */
    public int getLoadFactor(final ServerRef serverRef)
            throws ServerNotFoundException {
        Object obj = distributedTree.get(getServerLoadFactorPath(serverRef));
        if (obj == null) {
            throw new ServerNotFoundException("Unknown server: " + serverRef,
                    serverRef);
        }
        return (Integer) obj;
    }

    /**
     * Sets the load-factor for the server with the given address.
     *
     * @param serverRef
     *            a reference on a server
     * @param loadFactor
     *            the load-factor for the server with the given address
     */
    public void setLoadFactor(final ServerRef serverRef, final int loadFactor) {
        distributedTree.reset(getServerLoadFactorPath(serverRef), loadFactor);
    }

    /**
     * Initialize the statistics.
     */
    @Override
    protected void initStats() {
        synchronized (lock) {
            if (!distributedTree.exists(getNbClientsConnectedToProviderPath())) {
                distributedTree.add(getNbClientsConnectedToProviderPath(), 0);
            }
        }
    }

    /**
     * @return the numbers of clients connected to a provider of the cluster
     *         view
     */
    public int getNbClientsConnectedToProvider() {
        return (Integer) distributedTree
                .get(getNbClientsConnectedToProviderPath());
    }

    /**
     * Register a new client.
     *
     * @param uuid
     *            the Universally Unique Identifier of the client
     */
    public void registerClient(final UUID uuid) {
        // Warning! Not synchronized at cluster-level
        synchronized (lock) {
            if (distributedTree.exists(getClientUUIDPath(uuid.toString()))) {
                return;
            }
            distributedTree.add(getClientUUIDPath(uuid.toString()));
            distributedTree.set(getNbClientsConnectedToProviderPath(),
                    (Integer) distributedTree
                            .get(getNbClientsConnectedToProviderPath()) + 1);
        }
    }

    /*
     * ---------- Begin of definition for book-marks in the distributedTree
     * --------------------
     */

    /**
     * @param address address where to find the reference.
     * @param instRef reference
     * @return the path of the reference.
     */
    private String getInstAddrPath(final String address, final String instRef) {
        return address + "/" + instRef;
    }

    /**
     * @param objectName
     *            a name of object
     * @return the path in the distributed tree for a given object
     */
    private String getObjectPath(final String objectName) {
        return getObjectsPath() + "/" + objectName;
    }

    /**
     * @return the path in the distributed tree for the root
     */
    private String getRootPath() {
        return "/root";
    }

    /**
     * @return the path in the distributed tree for the objects
     */
    private String getObjectsPath() {
        return getRootPath() + "/objects";
    }

    /**
     * @param objectPath
     *            a path in the distributed tree for a object
     * @param protocol
     *            a name of protocol
     * @return a path in the distributed tree for instances of a given object
     *         and protocol
     */
    private String getProtocolPath(final String objectPath,
            final String protocol) {
        return objectPath + "/" + protocol;
    }

    /**
     * @param protocolPath
     *            a path in the distributed tree for instances of a given object
     *            and protocol
     * @param objectName
     *            a name of instance
     * @return a path in the distributed tree for a instance
     */
    private String getCMIReferencePath(final String protocolPath,
            final String objectName) {
        return protocolPath + "/" + objectName;
    }

    /**
     * @param cmiReference
     *            a reference on a instance
     * @return a path in the distributed tree for a instance
     */
    private String getCMIReferencePath(final CMIReference cmiReference) {
        return getProtocolPath(getObjectPath(cmiReference.getObjectName()),
                cmiReference.getServerRef().getProtocol())
                + "/"
                + cmiReference.getServerRef().getInetAddress().getHostAddress()
                + "_" + cmiReference.getServerRef().getPort();
    }

    /**
     * @return the path in the distributed tree for the addresses
     */
    private String getAddressesPath() {
        return getRootPath() + "/addresses";
    }

    /**
     * @param hostname
     *            a name of host
     * @param port
     *            a number of port
     * @return a path in the distributed tree for a given host and port
     */
    private String getAddressPath(final String hostname) {
        return getAddressesPath() + "/" + hostname;
    }

    /**
     * @return the path in the distributed tree for the delay
     */
    private String getDelayPath() {
        return getRootPath() + "/delay";
    }

    private String getServerConfigPath() {
        return getRootPath() + "/server_config";
    }

    private String getServerConfigPath(final ServerRef serverRef) {
        return getServerConfigPath() + "/"
        + serverRef.getInetAddress().getHostAddress() + "_" + serverRef.getPort();
    }

    /**
     * @return the path in the distributed tree for the black listed servers
     */
    private String getServerBlackListedPath(final ServerRef serverRef) {
        return getServerConfigPath(serverRef) + "/blacklisted";
    }

    /**
     * @return the path in the distributed tree for the load factors
     */
    private String getServerLoadFactorPath(final ServerRef serverRef) {
        return getServerConfigPath(serverRef) + "/loadfactor";
    }


    /**
     * @return the path in the distributed tree for the pool to empty
     */
    private String getPoolToEmptyPath() {
        return getRootPath() + "/pool_to_empty";
    }

    /**
     * @return the path in the distributed tree for the statistics
     */
    private String getStatsPath() {
        return getRootPath() + "/stats";
    }

    /**
     * @return the path in the distributed tree to get the number of connected
     *         clients
     */
    private String getNbClientsConnectedToProviderPath() {
        return getStatsPath() + "/nbClientsOnProvider";
    }

    /**
     * @return the path in the distributed tree to get the list of the client
     *         UUIDs
     */
    private String getClientUUIDPath() {
        return getStatsPath() + "/clientUUID";
    }

    /**
     * @return the path in the distributed tree to retrieve the given UUID
     */
    private String getClientUUIDPath(final String uuid) {
        return getClientUUIDPath() + "/" + uuid;
    }

    /**
     * @param objectName
     *            a name of object
     * @return
     */
    private String getPoolToEmptyPath(final String objectName) {
        return getPoolToEmptyPath() + "/" + objectName;
    }

    private String getGlobalFiltersPath() {
        return getRootPath() + "filters";
    }

    private String getGlobalFilterPath(final IFilter filter) {
        return getGlobalFiltersPath() + "/" + filter.getClass().getName();
    }

    private String getFilterPath(final String filterName) {
        return getGlobalFiltersPath() + "/" + filterName;
    }

    /*
     * --------------- End of definition for book-marks in the distributedTree
     * --------------------------
     */

    /*
     * ------------------ DistributedTree.DistributedTreeListener interface
     * ------------
     */

    public void nodeAdded(final String fqn, final Serializable element) {
        LOGGER.debug("{0} has been added", fqn);
        if (element != null && element instanceof DistributedObjectInfo) {
            DistributedObjectInfo distributedObjectInfo = (DistributedObjectInfo) element;
            String objectName = distributedObjectInfo.getObjectName();
            if (isWatched(objectName)) {
                try {
                    updatePolicy(objectName);
                } catch (Exception e) {
                    LOGGER.warn(
                            "Cannot update LBPolicy for object with name {0}",
                            objectName, e);
                }
                try {
                    updatePool(null, distributedObjectInfo);
                } catch (Exception e) {
                    LOGGER.warn(
                            "Cannot update the pool configuration for object with name {0}",
                            objectName, e);
                }
            }
        } else if (element != null && element instanceof ArchiveWrapper) {
            if (policyStrategyLoader != null) {
                LOGGER.debug("Handling new archive notification for Load balancing policies/strategies");
             ArchiveWrapper archive = (ArchiveWrapper) element;
             archive.setToBeInstalled(!archive.getArchiveId().toString().contains(this.getUUID().toString()));
             policyStrategyLoader.loadArchive(
                     ((IServerConfig) getConfig()).getDeployDirectory(), archive);
            } else {
                LOGGER.debug("No policy/strategy manager defined.");
            }
        }
    }

    public void nodeModified(final String fqn, final Serializable oldElement,
            final Serializable newElement) {
        LOGGER.debug("{0} has been modified", fqn);
        LOGGER.debug("Old element: {0} - New element: {1}", oldElement,
                newElement);
        // Updates local data if needed
        if (newElement != null && newElement instanceof DistributedObjectInfo) {
            if(oldElement == null) {
                throw new IllegalStateException("Cannot modify the empty node at path " + fqn);
            }
            DistributedObjectInfo oldDistributedObjectInfo = (DistributedObjectInfo) oldElement;
            DistributedObjectInfo newDistributedObjectInfo = (DistributedObjectInfo) newElement;
            String objectName = newDistributedObjectInfo.getObjectName();
            if (isWatched(objectName)) {
                // Checks if LB policy has changed
                PolicyData newLBPolicyData = newDistributedObjectInfo
                        .getPolicyData();
                if (!oldDistributedObjectInfo.getPolicyData().equals(newLBPolicyData)) {
                    try {
                        updatePolicy(objectName);
                    } catch (Exception e) {
                        LOGGER.warn(
                                "Cannot update LBPolicy for object with name {0}",
                                objectName, e);
                    }
                }

                try {
                    updatePool(oldDistributedObjectInfo, newDistributedObjectInfo);
                } catch (Exception e) {
                    LOGGER.warn(
                            "Cannot update the pool configuration for object with name {0}",
                            objectName, e);
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.ow2.util.cluster.jgroups.IDistributedTree.DistributedTreeListener#nodeRemoved(java.lang.String)
     */
    public void nodeRemoved(final String fqn) {
        LOGGER.debug("{0} has been removed", fqn);
        String arcPath = getArchivesPath();
        if (fqn.contains(arcPath)) {
            String archiveId = fqn.replace(arcPath, "");
            policyStrategyLoader.unLoadArchive(archiveId);
        }
    }

    /*
     * ---------------- End of DistributedTree.DistributedTreeListener interface
     * --------
     */

    /* ------------------ DistributedTree.ViewListener interface ------------ */

    public void viewChange(	final Vector<SocketAddress> newMbrs,
            				final Vector<SocketAddress> oldMbrs) {

        LOGGER.debug("view has changed");
        // Remove the reference on the old objects
        for (SocketAddress oldMbr : oldMbrs) {
            String hostname = oldMbr.toString();
            LOGGER.debug("Search instances on {0}", hostname);
            String addrPath = getAddressPath(hostname);
            Vector<String> childrenNames = distributedTree
                    .getChildrenNames(addrPath);
            for (String instAddrName : childrenNames) {
                String instAddrPath = getInstAddrPath(addrPath, instAddrName);
                String pathToDelete = (String) distributedTree
                        .get(instAddrPath);
                LOGGER.debug("Marking {0} to delete", pathToDelete);
                pathsToDelete.add(pathToDelete);
            }
            LOGGER.debug("Marking {0} to delete...", addrPath);
            pathsToDelete.add(addrPath);
            resumableRunnable.resumeExecution();
        }
    }

    /* ---------------- End of DistributedTree.ViewListener interface -------- */

    /**
     * To clean the distributed tree.
     */
    private class TreeCleaner implements ResumableRunnable {
        private volatile boolean suspended;
        private volatile boolean stopped;

        public void run() {
            stopped = false;
            while (!stopped) {
                synchronized (this) {
                    try {
                        suspended = true;
                        while (suspended && !stopped) {
                            wait();
                        }
                    } catch (InterruptedException e) {
                        // Restore interrupted status
                        Thread.currentThread().interrupt();
                    }
                }
                LOGGER.debug("Cleaning the distributed tree...");
                synchronized (pathsToDelete) {
                    Iterator<String> it = pathsToDelete.iterator();
                    while (it.hasNext()) {
                        String pathToDelete = it.next();
                        removePath(pathToDelete);
                        it.remove();
                    }
                }
            }
            LOGGER.debug("Garbage collector stopped");
        }

        public void resumeExecution() {
            LOGGER.debug("Garbage collector resumed");
            synchronized (this) {
                suspended = false;
                notifyAll();
            }
        }

        public void stopExecution() {
            LOGGER.debug("Garbage collector stopping...");
            synchronized (this) {
                stopped = true;
                notifyAll();
            }
        }
    }

    @Override
    public void broadCastArchive(final Object archiveId,
            final byte[] bytesOfFile) {
        LOGGER.debug("Sharing archive with id {0} to my cluster", archiveId);
        putArchiveInTheTree(archiveId.toString(), bytesOfFile);
    }

    /**
     * Registering the archive in the distributed tree
     *
     * @param id
     * @param bytesOfFile
     */
    private void putArchiveInTheTree(final String archiveId,
            final byte[] bytesOfFile) {
        String archivePath = buildArchivePath(archiveId);
        LOGGER
                .debug(
                        " Putting the load balancing archive with id {0} on path {1} of the distributed tree",
                        archiveId, archivePath);
        distributedTree.add(archivePath, new ArchiveWrapper(bytesOfFile,
                archiveId));
        LOGGER
                .debug(
                        " The Load balancing archive with id {0} is shared with path {1}",
                        archiveId, archivePath);
    }

    /**
     * Build path for given archive. result for <code>my_archive</code> is
     * <code>root/archives/my_archive</code>
     *
     * @param id
     * @return
     */
    private String buildArchivePath(final String id) {
        // TODO Auto-generated method stub
        return getArchivesPath() + "/" + id;
    }

    /**
     * Get the path for archives.
     *
     * @return
     */
    private String getArchivesPath() {
        return getRootPath() + "/archives";
    }

    /**
     * Don't share archive anymore.
     *
     * @param location
     *            File location
     */
    @Override
    public void removeDistributedArchive(final Object id) {
        String arcPath = buildArchivePath(id.toString());
        distributedTree.remove(arcPath);
    }

    /**
     * Checks if the given archive is already distributed
     * @param archiveId the Id of the archive to check.
     * @return true if the archive is already distributed, false otherwise
     */
    @Override
    public boolean isAlreadyDistributed(final Object archiveId) {
        // TODO Auto-generated method stub
        String arcPath = buildArchivePath(archiveId.toString());
        return distributedTree.get(arcPath) != null;
    }


    /**
     * Return the global filters.
     * @return a list of global filters
     */
    public List<IFilter> getGlobalFilters() {
        List<IFilter> filters = new ArrayList<IFilter>();
        for(String strategyName : distributedTree.getChildrenNames(getGlobalFiltersPath())) {
            Serializable s = distributedTree.get(getFilterPath(strategyName));
            if(s != null && s instanceof IdentityFilter) {
                filters.add((IFilter) s);
            }
        }
        return filters;
    }

    /**
     * Add a new global filter.
     * @param filter a global filter
     * @throws IllegalArgumentException if the provided filter is not serializable
     */
    public void addGlobalFilter(final IFilter filter) throws IllegalArgumentException {
        if(!(filter instanceof Serializable)) {
            throw new IllegalArgumentException("The provided global filter is not serializable.");
        }
        distributedTree.add(getGlobalFilterPath(filter), (Serializable) filter);
    }

    /**
     * Remove a global filter.
     * @param filter a global filter
     */
    public void removeGlobalFilter(final IFilter filter) {
        distributedTree.remove(getGlobalFilterPath(filter));
    }

    public long getDateOfConfiguration() {
        // TODO Auto-generated method stub
        return -1;
    }

}
